//
//	MME+Symbols.m -- merge engine symbol table and variable handlers
//		Written by Don Yacktman Copyright (c) 1995 by Don Yacktman.
//				Version 1.0.  All rights reserved.
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	


#import <misckit/misckit.h>
#import <misckit/MiscMergeEngine.h>
#import <misckit/MiscMergeTemplate.h>
#import <misckit/MiscIfStack.h>


@implementation MiscMergeEngine (Symbols)

// Handling the local symbol table

- resetSymbolTable
/*" Clears the local symbol table.  Returns self.
"*/
{	// we don't free values--this could leak memory
	if (symbolTable) {
		[symbolTable freeObjects];
		[symbolTable free];
	}
	symbolTable = [[MiscDictionary alloc] init];
	return self;
}

- (MiscString *)symbolForKey:(MiscString *)name
/*" Attempts to find a symbol in the symbol table that corresponds to
%{name}.  If found, the value for that symbol is returned, if not, then
nil is returned.  The search is conducted through the local symbol
table, through all parent merges' symbol tables, and then through the
global symbol table.
"*/
{ // if not available locally, we check the global table.
	if ([symbolTable isKey:name]) {
		// we have it, so return that.
		return [symbolTable valueForKey:name];
	} else {
		if (parentMerge) {
			// if parent doesn't have it, it will try it's
			// parent, backing up until the global table is hit.
			return [parentMerge symbolForKey:name];
		}
	}
	// if the parent doesn't exist and we don't
	// have it, then we check the globals.
	return [[self class] globalSymbolForKey:name];
}

- setSymbol:(MiscString *)name toValue:(MiscString *)value
/*" Adds the symbol %{name} to the local symbol table with %{value} as
its value.  Returns self.
"*/
{
	[value squashSpaces]; // just to be safe
	[symbolTable insertKey:name value:value];
	return self;
}

- (MiscString *)getField:(MiscString *)fieldName
/*" Attempts to resolve a field name.  If found in the merge dictionary,
then the value in the dictionary is returned.  If not found there, then
a search through the symbol tables is conducted.  If there are no local
or global symbols named %{fieldName} then %{fieldName} is returned.  If
there are local or global symbols, however, then an attempt is made to
resolve their values to a key in the merge dictionary.  If the local or
global symbols exist, but cannot be resolved into values in the merge
dictionary, then they local/global value is returned.

This complex search allows aliases to be created for various merge fields
so that they may be accessed by different names.  It also allows
redirection--if a field is missing in a particular merge, the global
or local symbol tables can suggest another field to use in its place.
Finally, it allows setting of default values in the local or global
symbol table.  If the field is missing from the merge dictionary, then a
default stored in the symbol tables can be used.
"*/
{
	MiscString *fieldString = nil;
	MiscString *fieldString2 = nil;

	[fieldName squashSpaces];
	// if in the merge record, return it
	if ([dictionary isKey:fieldName]) {
		return [dictionary valueForKey:fieldName];
	}
	
	fieldString = [self symbolForKey:fieldName];
	// while loop allows for indirection
	while (fieldString) {
		if ([dictionary isKey:fieldString]) {
			return [dictionary valueForKey:fieldString];
		}
		fieldString2 = fieldString;
		fieldString = [self symbolForKey:fieldString];
	}
	if (fieldString2) return fieldString2;

	// Following if condition is a temporary fix for leaving out spurious
	// "blank" fields at the end of the merge template.  It should really
	// only have to check the state of "leaveDelimiters", not "fieldName".
	if (leaveDelimiters && (fieldName != NULL) && (![fieldName emptyString])) {
		MiscString *output = [MiscString new];
		[output setFromFormat:"%c%s%c",
				[MiscMergeTemplate startFieldDelimiter],
				[fieldName stringValue],
				[MiscMergeTemplate endFieldDelimiter]];
		return output; // these will LEAK, since not coming from dictionary.
		// (other strings returned by this method shouldn't be freed!)
		// No way around it until we get the foundation kit.  :-(
	}
	return fieldName; // treat as a literal if not found
}

- setLeaveDelimiters:(BOOL)aFlag
/*" Tells engine how to handle unresolveable merge fields.  If %{aFlag}
is YES, then the merge delimiters will be put around the field name and
that will be returned.  If NO, then the field name will be returned
without the delimiters on.  (That is the default.)  Returns self.
"*/
{
	leaveDelimiters = aFlag;
	return self;
}

- (BOOL)leaveDelimiters
/*" Returns how the engine is set to handle unresolveable merge fields.
"*/
{
	return leaveDelimiters;
}

- resetVariables
/*" Empties out the merge variables.  Returns self.
"*/
{	// we don't free values--this could leak memory
	if (variables) {
		[variables freeObjects];
		[variables free];
	}
	variables = [[MiscDictionary alloc] init];
	return self;
}

- getVariableNamed:(MiscString *)name
/*" Returns the merge variable named %{name}.  Returns nil if not found.
"*/
{
	return [variables valueForKey:name];
}

- setVariableNamed:(MiscString *)name to:aValue
/*" Sets the merge variable named %{name} to %{aValue}.  Returns self.
"*/
{
	[variables insertKey:name value:aValue];
	return self;
}

// This is a "special" variable available to all mergers; we make
// sure that it exists.  Commands can obviously still install their
// own variables as well...
- ifStack
/*" Returns the special ªif stackº merge variable, creating it if
necessary.  The ªif stackº is used by the if/else/endif commands
and also used to control turning the output of the merge on and off.
"*/
{
	static id ifStackKey = nil;
	id ifStack;

	if (!ifStackKey) ifStackKey = [MiscString newWithString:"_Misc_ifStack"];
	ifStack = [self getVariableNamed:ifStackKey];
	if (!ifStack) {
		ifStack = [[MiscIfStack alloc] init];
		[self setVariableNamed:ifStackKey to:ifStack];
	}
	return ifStack;
}

@end
